Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

热执行代码 #303

Merged
merged 1 commit into from
Jun 9, 2024
Merged

热执行代码 #303

merged 1 commit into from
Jun 9, 2024

Conversation

NMSAzulX
Copy link
Collaborator

@NMSAzulX NMSAzulX commented Jun 9, 2024

Summary by CodeRabbit

  • New Features

    • Introduced dynamic code reloading capabilities.
    • Added methods for building and running C# projects dynamically.
    • Implemented file monitoring for C# project files.
  • Enhancements

    • Improved error handling and debug output during project builds.
    • Enhanced path determination for C# projects based on execution context.

Copy link

coderabbitai bot commented Jun 9, 2024

Walkthrough

The recent updates to the Natasha.CSharp.Extension.HotExecutor project introduce new functionalities for dynamic code execution and monitoring in C#. Key additions include methods for building projects in different configurations, appending arguments, and running the project dynamically. Enhancements also include file watching capabilities for monitoring changes in project files and executing actions based on these changes.

Changes

File Path Change Summary
.../Natasha.CSharp.Extension.HotExecutor.csproj Introduced configuration settings for a .NET Standard 2.0 project.
.../ProjectDynamicProxy.cs Added methods for building in release/debug, appending arguments, and running projects dynamically.
.../VSCSharpFolder.cs Added static properties for various paths related to C# projects.
.../VSCSharpProcessor.cs Added private fields and methods for building and running C# projects with dotnet.exe.
.../VSCSharpProjectFileWatcher.cs Added classes for monitoring changes in C# project files and executing actions based on these changes.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ProjectDynamicProxy
    participant VSCSharpProcessor
    participant VSCSharpFolder

    User->>ProjectDynamicProxy: Run()
    ProjectDynamicProxy->>VSCSharpProcessor: BuildProject()
    VSCSharpProcessor->>VSCSharpFolder: Retrieve project paths
    VSCSharpFolder-->>VSCSharpProcessor: Return paths
    VSCSharpProcessor-->>ProjectDynamicProxy: Build complete
    ProjectDynamicProxy->>VSCSharpProcessor: Run()
    VSCSharpProcessor-->>ProjectDynamicProxy: Execution complete
    ProjectDynamicProxy-->>User: Run complete
Loading

Poem

In the land of code so bright and clear,
New methods bloom, bringing cheer.
Projects build in debug or release,
Arguments flow with graceful ease.
File watchers guard with vigilant eyes,
As dynamic proxies reach for the skies.
CodeRabbit smiles with a knowing grin,
For in this update, new adventures begin.


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

Share
Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai generate interesting stats about this repository and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (invoked as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Configration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@NMSAzulX
Copy link
Collaborator Author

NMSAzulX commented Jun 9, 2024

@coderabbitai review

Copy link

coderabbitai bot commented Jun 9, 2024

Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@NMSAzulX
Copy link
Collaborator Author

NMSAzulX commented Jun 9, 2024

未检测到合适的 ISSUE 推荐给您。感谢您的反馈!

该条自动推荐信息来自于 nms-bot.

Copy link

github-actions bot commented Jun 9, 2024

UT Test - Ubuntu

1 tests   1 ✅  0s ⏱️
1 suites  0 💤
1 files    0 ❌

Results for commit b680ec5.

Copy link

github-actions bot commented Jun 9, 2024

UT Test - Windows

1 tests   1 ✅  0s ⏱️
1 suites  0 💤
1 files    0 ❌

Results for commit b680ec5.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Outside diff range and nitpick comments (5)
src/Natasha.CSharp/Extension/Natasha.CSharp.Extension.HotExecutor/ProjectDynamicProxy.cs (5)

254-257: Add documentation for BuildWithRelease.

Consider adding XML documentation comments to explain the purpose and effect of setting IsRelease to true.


259-261: Add documentation for BuildWithDebug.

Consider adding XML documentation comments to explain the purpose and effect of setting IsRelease to false.


323-326: Add documentation for AppendArgs(string arg).

Consider adding XML documentation comments to explain the purpose and usage of this method.


327-330: Add documentation for AppendArgs(params string[] args).

Consider adding XML documentation comments to explain the purpose and usage of this method.


331-333: Add documentation for ClearArgs.

Consider adding XML documentation comments to explain the purpose and usage of this method.

Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

Commits

Files that changed from the base of the PR and between 87e69bf and b680ec5.

Files selected for processing (5)
  • src/Natasha.CSharp/Extension/Natasha.CSharp.Extension.HotExecutor/Natasha.CSharp.Extension.HotExecutor.csproj (1 hunks)
  • src/Natasha.CSharp/Extension/Natasha.CSharp.Extension.HotExecutor/ProjectDynamicProxy.cs (1 hunks)
  • src/Natasha.CSharp/Extension/Natasha.CSharp.Extension.HotExecutor/VSCSharpFolder.cs (1 hunks)
  • src/Natasha.CSharp/Extension/Natasha.CSharp.Extension.HotExecutor/VSCSharpProcessor.cs (1 hunks)
  • src/Natasha.CSharp/Extension/Natasha.CSharp.Extension.HotExecutor/VSCSharpProjectFileWatcher.cs (1 hunks)
Additional comments not posted (5)
src/Natasha.CSharp/Extension/Natasha.CSharp.Extension.HotExecutor/Natasha.CSharp.Extension.HotExecutor.csproj (2)

3-15: The updates to the PropertyGroup are well-defined and align with the project's requirements for .NET Standard 2.0, including package metadata and compiler settings.


17-19: The project reference to Natasha.CSharp.Compiler is correctly specified.

src/Natasha.CSharp/Extension/Natasha.CSharp.Extension.HotExecutor/VSCSharpFolder.cs (1)

16-30: The static constructor correctly initializes all necessary paths using the current execution context, which is crucial for the dynamic nature of the project.

src/Natasha.CSharp/Extension/Natasha.CSharp.Extension.HotExecutor/VSCSharpProcessor.cs (2)

14-28: The constructor of VSCSharpProcessor is well-structured, initializing necessary fields and setting up the process start information correctly.


30-35: The ResetOutputInfo method dynamically configures the output paths and build arguments, which is essential for flexible project builds.

Comment on lines +335 to +371
public static void Run(string proxyMethodName = "ProxyMain", string? argumentsMethodName= "ProxyMainArguments")
{
_proxyMethodName = proxyMethodName;
_argumentsMethodName = argumentsMethodName;
var srcCodeFiles = Directory.GetFiles(VSCSharpFolder.MainCsprojPath, "*.cs", SearchOption.AllDirectories);

foreach (var file in srcCodeFiles)
{
if (CheckFileAvailiable(file))
{
var content = ReadFile(file);
//Console.WriteLine(file);
//Console.WriteLine(content);
//Console.WriteLine("------------");
var tree = NatashaCSharpSyntax.ParseTree(content, null);
var mainMethod = tree.GetRoot().DescendantNodes()
.OfType<MethodDeclarationSyntax>()
.FirstOrDefault(m => m.Identifier.Text == "Main");

if (mainMethod != null)
{
ClassDeclarationSyntax? parentClass = mainMethod.Parent as ClassDeclarationSyntax ?? throw new Exception("获取 Main 方法类名出现错误!");
_className = parentClass.Identifier.Text;

var proxyMethod = tree.GetRoot().DescendantNodes()
.OfType<MethodDeclarationSyntax>()
.FirstOrDefault(m => m.Identifier.Text == proxyMethodName);

if (proxyMethod == null)
{
throw new Exception($"{_className} 中未找到 {proxyMethodName} 代理方法!");
}
_mainFile = file;
}
_fileCache[file] = tree;
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactor Run method to improve readability and error handling.

The Run method is quite lengthy and does multiple things. Consider breaking it down into smaller, more focused methods. Additionally, improve error handling by not just throwing exceptions but also logging them or handling them gracefully.

- throw new Exception($"{_className} 中未找到 {proxyMethodName} 代理方法!");
+ LogError($"Method {proxyMethodName} not found in class {_className}."); // Assuming LogError is a method that logs errors.

Committable suggestion was skipped due to low confidence.

Comment on lines +376 to +405
private static string ReadFile(string file)
{
FileStream stream;
do
{
try
{
stream = new(file, FileMode.Open, FileAccess.Read, FileShare.Read);
StringBuilder stringBuilder = new();
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
{
stringBuilder.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
}
stream.Dispose();
return stringBuilder.ToString();
}
catch (Exception)
{
#if DEBUG
Console.WriteLine("命中文件锁!");
#endif

Thread.Sleep(200);
}


} while (true);

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactor ReadFile to handle exceptions more gracefully.

The current implementation of ReadFile uses a loop that can potentially become an infinite loop if the file remains locked. Consider adding a maximum retry count and more detailed error logging.

- while (true)
+ for (int retry = 0; retry < 10; retry++)
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private static string ReadFile(string file)
{
FileStream stream;
do
{
try
{
stream = new(file, FileMode.Open, FileAccess.Read, FileShare.Read);
StringBuilder stringBuilder = new();
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
{
stringBuilder.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
}
stream.Dispose();
return stringBuilder.ToString();
}
catch (Exception)
{
#if DEBUG
Console.WriteLine("命中文件锁!");
#endif
Thread.Sleep(200);
}
} while (true);
private static string ReadFile(string file)
{
FileStream stream;
for (int retry = 0; retry < 10; retry++)
{
try
{
stream = new(file, FileMode.Open, FileAccess.Read, FileShare.Read);
StringBuilder stringBuilder = new();
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
{
stringBuilder.Append(Encoding.UTF8.GetString(buffer, 0, bytesRead));
}
stream.Dispose();
return stringBuilder.ToString();
}
catch (Exception)
{
#if DEBUG
Console.WriteLine("命中文件锁!");
#endif
Thread.Sleep(200);
}
}
throw new IOException("Failed to read file after multiple attempts.");

Comment on lines +408 to +455
private static Task HotExecute()
{
try
{
_builderCache.WithRandomAssenblyName();
_builderCache.SyntaxTrees.Clear();
_builderCache.SyntaxTrees.AddRange(_fileCache.Values);
if (IsRelease)
{
_builderCache.WithReleaseCompile();
}
else
{
_builderCache.WithDebugCompile();
}
var assembly = _builderCache.GetAssembly();
var types = assembly.GetTypes();
var typeInfo = assembly.GetTypeFromShortName(_className!);
var methods = typeInfo.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
if (methods.Any(item => item.Name == _argumentsMethodName))
{
_args.Clear();
var argumentMethodInfo = methods.First(item => item.Name == _argumentsMethodName);
argumentMethodInfo.Invoke(null, []);
}

var proxyMethodInfo = typeInfo.GetMethod(_proxyMethodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
object? instance = null;
if (!proxyMethodInfo.IsStatic)
{
instance = Activator.CreateInstance(typeInfo);
}
if (proxyMethodInfo.GetParameters().Length == 1)
{
proxyMethodInfo.Invoke(instance, [_args.ToArray()]);
}
else
{
proxyMethodInfo.Invoke(instance, []);
}

}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return Task.CompletedTask;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure proper exception handling in HotExecute.

The method HotExecute catches a general exception and only logs the message. This might not be sufficient for debugging. Consider logging the full stack trace or handling specific exceptions differently.

- Console.WriteLine(ex.Message);
+ Console.WriteLine($"Error during hot execution: {ex}");
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private static Task HotExecute()
{
try
{
_builderCache.WithRandomAssenblyName();
_builderCache.SyntaxTrees.Clear();
_builderCache.SyntaxTrees.AddRange(_fileCache.Values);
if (IsRelease)
{
_builderCache.WithReleaseCompile();
}
else
{
_builderCache.WithDebugCompile();
}
var assembly = _builderCache.GetAssembly();
var types = assembly.GetTypes();
var typeInfo = assembly.GetTypeFromShortName(_className!);
var methods = typeInfo.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
if (methods.Any(item => item.Name == _argumentsMethodName))
{
_args.Clear();
var argumentMethodInfo = methods.First(item => item.Name == _argumentsMethodName);
argumentMethodInfo.Invoke(null, []);
}
var proxyMethodInfo = typeInfo.GetMethod(_proxyMethodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
object? instance = null;
if (!proxyMethodInfo.IsStatic)
{
instance = Activator.CreateInstance(typeInfo);
}
if (proxyMethodInfo.GetParameters().Length == 1)
{
proxyMethodInfo.Invoke(instance, [_args.ToArray()]);
}
else
{
proxyMethodInfo.Invoke(instance, []);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return Task.CompletedTask;
}
private static Task HotExecute()
{
try
{
_builderCache.WithRandomAssenblyName();
_builderCache.SyntaxTrees.Clear();
_builderCache.SyntaxTrees.AddRange(_fileCache.Values);
if (IsRelease)
{
_builderCache.WithReleaseCompile();
}
else
{
_builderCache.WithDebugCompile();
}
var assembly = _builderCache.GetAssembly();
var types = assembly.GetTypes();
var typeInfo = assembly.GetTypeFromShortName(_className!);
var methods = typeInfo.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
if (methods.Any(item => item.Name == _argumentsMethodName))
{
_args.Clear();
var argumentMethodInfo = methods.First(item => item.Name == _argumentsMethodName);
argumentMethodInfo.Invoke(null, []);
}
var proxyMethodInfo = typeInfo.GetMethod(_proxyMethodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
object? instance = null;
if (!proxyMethodInfo.IsStatic)
{
instance = Activator.CreateInstance(typeInfo);
}
if (proxyMethodInfo.GetParameters().Length == 1)
{
proxyMethodInfo.Invoke(instance, [_args.ToArray()]);
}
else
{
proxyMethodInfo.Invoke(instance, []);
}
}
catch (Exception ex)
{
Console.WriteLine($"Error during hot execution: {ex}");
}
return Task.CompletedTask;
}

Comment on lines +32 to +44
static string FindFileDirectory(DirectoryInfo csprojDirectory, string suffix)
{
DirectoryInfo directory = new(csprojDirectory.FullName);
while (directory != directory.Root)
{
if (directory.GetFiles(suffix).Length > 0)
{
return directory.FullName;
}
directory = directory.Parent;
}
throw new Exception("没有找到 sln 根文件!");
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The FindFileDirectory method is robust in finding directories based on file suffixes. Consider enhancing the exception message to include the missing suffix for better debugging.

- throw new Exception("没有找到 sln 根文件!");
+ throw new Exception($"没有找到包含后缀 {suffix} 的根文件!");
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
static string FindFileDirectory(DirectoryInfo csprojDirectory, string suffix)
{
DirectoryInfo directory = new(csprojDirectory.FullName);
while (directory != directory.Root)
{
if (directory.GetFiles(suffix).Length > 0)
{
return directory.FullName;
}
directory = directory.Parent;
}
throw new Exception("没有找到 sln 根文件");
}
static string FindFileDirectory(DirectoryInfo csprojDirectory, string suffix)
{
DirectoryInfo directory = new(csprojDirectory.FullName);
while (directory != directory.Root)
{
if (directory.GetFiles(suffix).Length > 0)
{
return directory.FullName;
}
directory = directory.Parent;
}
throw new Exception($"没有找到包含后缀 {suffix} 的根文件");
}

Comment on lines +38 to +78
public ValueTask<bool> BuildProject()
{
try
{
ResetOutputInfo();
Encoding utf8 = Encoding.UTF8;
using var process = new Process();
process.StartInfo = _builder;
process.EnableRaisingEvents = true;
process.OutputDataReceived += (sender, e) =>
{
if (!string.IsNullOrEmpty(e.Data))
{
Console.WriteLine(utf8.GetString(utf8.GetBytes(e.Data)));
}
};
process.ErrorDataReceived += (sender, e) =>
{
if (!string.IsNullOrEmpty(e.Data))
{
Console.WriteLine(utf8.GetString(utf8.GetBytes(e.Data)));
}
};

process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
#if DEBUG
Console.WriteLine($"正在执行 {_builder.FileName} {_builder.Arguments}");
#endif
process.WaitForExit();
return new ValueTask<bool>(process.ExitCode == 0);
}
catch (Exception ex)
{
#if DEBUG
Console.WriteLine($"执行出错 {ex.Message}");
#endif
}
return new ValueTask<bool>(false);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The BuildProject method is robust, handling process execution and output effectively. Consider enhancing error handling by logging more specific error details or categorizing errors.

- Console.WriteLine($"执行出错 {ex.Message}");
+ Console.WriteLine($"执行出错: {ex.GetType().Name} - {ex.Message}");
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public ValueTask<bool> BuildProject()
{
try
{
ResetOutputInfo();
Encoding utf8 = Encoding.UTF8;
using var process = new Process();
process.StartInfo = _builder;
process.EnableRaisingEvents = true;
process.OutputDataReceived += (sender, e) =>
{
if (!string.IsNullOrEmpty(e.Data))
{
Console.WriteLine(utf8.GetString(utf8.GetBytes(e.Data)));
}
};
process.ErrorDataReceived += (sender, e) =>
{
if (!string.IsNullOrEmpty(e.Data))
{
Console.WriteLine(utf8.GetString(utf8.GetBytes(e.Data)));
}
};
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
#if DEBUG
Console.WriteLine($"正在执行 {_builder.FileName} {_builder.Arguments}");
#endif
process.WaitForExit();
return new ValueTask<bool>(process.ExitCode == 0);
}
catch (Exception ex)
{
#if DEBUG
Console.WriteLine($"执行出错 {ex.Message}");
#endif
}
return new ValueTask<bool>(false);
}
public ValueTask<bool> BuildProject()
{
try
{
ResetOutputInfo();
Encoding utf8 = Encoding.UTF8;
using var process = new Process();
process.StartInfo = _builder;
process.EnableRaisingEvents = true;
process.OutputDataReceived += (sender, e) =>
{
if (!string.IsNullOrEmpty(e.Data))
{
Console.WriteLine(utf8.GetString(utf8.GetBytes(e.Data)));
}
};
process.ErrorDataReceived += (sender, e) =>
{
if (!string.IsNullOrEmpty(e.Data))
{
Console.WriteLine(utf8.GetString(utf8.GetBytes(e.Data)));
}
};
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
#if DEBUG
Console.WriteLine($"正在执行 {_builder.FileName} {_builder.Arguments}");
#endif
process.WaitForExit();
return new ValueTask<bool>(process.ExitCode == 0);
}
catch (Exception ex)
{
#if DEBUG
Console.WriteLine($"执行出错: {ex.GetType().Name} - {ex.Message}");
#endif
}
return new ValueTask<bool>(false);
}

Comment on lines +80 to +118
public ValueTask<bool> Run()
{
try
{
if (_process != null)
{
_process.Kill();
_process.Dispose();

}

_process = new Process()
{
StartInfo = new()
{
FileName = VSCSharpFolder.ExecuteName,
WorkingDirectory = _outpuNewAppFolder,
CreateNoWindow = true,
UseShellExecute = true,
}
};
#if DEBUG
Console.WriteLine($"执行: {Path.Combine(_outpuNewAppFolder, VSCSharpFolder.ExecuteName)}");
#endif
_process.Start();
// 等待一小段时间,让进程有机会启动
System.Threading.Thread.Sleep(1000);
Process[] processes = Process.GetProcessesByName(Path.GetFileNameWithoutExtension(_outputNewExeFile));
if (processes.Length > 0)
{
return new ValueTask<bool>(true);
}
}
catch (Exception e)
{
Console.WriteLine("Error starting the process: " + e.Message);
}
return new ValueTask<bool>(false);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Run method effectively manages the execution of processes. Consider improving the process management logic to handle multiple instances more gracefully.

- if (processes.Length > 0)
+ if (processes.Any(p => p.HasExited == false))
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public ValueTask<bool> Run()
{
try
{
if (_process != null)
{
_process.Kill();
_process.Dispose();
}
_process = new Process()
{
StartInfo = new()
{
FileName = VSCSharpFolder.ExecuteName,
WorkingDirectory = _outpuNewAppFolder,
CreateNoWindow = true,
UseShellExecute = true,
}
};
#if DEBUG
Console.WriteLine($"执行: {Path.Combine(_outpuNewAppFolder, VSCSharpFolder.ExecuteName)}");
#endif
_process.Start();
// 等待一小段时间,让进程有机会启动
System.Threading.Thread.Sleep(1000);
Process[] processes = Process.GetProcessesByName(Path.GetFileNameWithoutExtension(_outputNewExeFile));
if (processes.Length > 0)
{
return new ValueTask<bool>(true);
}
}
catch (Exception e)
{
Console.WriteLine("Error starting the process: " + e.Message);
}
return new ValueTask<bool>(false);
}
public ValueTask<bool> Run()
{
try
{
if (_process != null)
{
_process.Kill();
_process.Dispose();
}
_process = new Process()
{
StartInfo = new()
{
FileName = VSCSharpFolder.ExecuteName,
WorkingDirectory = _outpuNewAppFolder,
CreateNoWindow = true,
UseShellExecute = true,
}
};
#if DEBUG
Console.WriteLine($"执行: {Path.Combine(_outpuNewAppFolder, VSCSharpFolder.ExecuteName)}");
#endif
_process.Start();
// 等待一小段时间,让进程有机会启动
System.Threading.Thread.Sleep(1000);
Process[] processes = Process.GetProcessesByName(Path.GetFileNameWithoutExtension(_outputNewExeFile));
if (processes.Any(p => p.HasExited == false))
{
return new ValueTask<bool>(true);
}
}
catch (Exception e)
{
Console.WriteLine("Error starting the process: " + e.Message);
}
return new ValueTask<bool>(false);
}

Comment on lines +106 to +210
_notify = notifyAction;
if (!watcherCache.ContainsKey(csprojPath))
{
if (File.Exists(csprojPath))
{
(FileSystemWatcher csprojWatcher, FileSystemWatcher csFileWatcher) watcher = new();
if (!watcherCache.TryAdd(csprojPath, watcher))
{
return;
}

#if DEBUG
Console.WriteLine($"部署对 {csprojPath} 的监控. ");
#endif
var folder = Path.GetDirectoryName(csprojPath);
var fileName = Path.GetFileName(csprojPath);
var binFolder = Path.Combine(folder, "bin");
var objFolder = Path.Combine(folder, "obj");
FileSystemEventHandler handler = (sender, e) => {
if (!e.FullPath.StartsWith(binFolder) && !e.FullPath.StartsWith(objFolder))
{
_notify?.Invoke();
}
};
RenamedEventHandler reNameHandler = (sender, e) => {
if (!e.FullPath.StartsWith(binFolder) && !e.FullPath.StartsWith(objFolder))
{
_notify?.Invoke();
}
};

var csfileWatcher = new FileSystemWatcher()
{
Path = folder,
Filter = "*.cs",
EnableRaisingEvents = true,
IncludeSubdirectories = true
};
csfileWatcher.Changed += handler;
csfileWatcher.Deleted += handler;
csfileWatcher.Created += handler;
csfileWatcher.Renamed += reNameHandler;

var csprojWatcher = new FileSystemWatcher()
{
Path = folder,
Filter = fileName,
EnableRaisingEvents = true,
IncludeSubdirectories = false,
};

csprojWatcher.Changed += handler;
csprojWatcher.Renamed += reNameHandler;
csprojWatcher.Deleted += (sender, e) =>
{
if (e.FullPath == csprojPath)
{
csprojWatcher.Dispose();
csfileWatcher.Dispose();
watcherCache.TryRemove(csprojPath, out var _);
}
};
watcherCache[csprojPath] = (csprojWatcher, csfileWatcher);
MonitorDependencyProject(folder, csprojPath, watcherCache, notifyAction);
}
else
{
throw new Exception($"未找到 {csprojPath} 文件");
}
}
}

private void MonitorDependencyProject(string foldPath, string csprojPath, ConcurrentDictionary<string, (FileSystemWatcher csprojWatcher, FileSystemWatcher csFileWatcher)> watcherList, Action? notifyAction)
{
try
{
XmlDocument doc = new();
doc.Load(csprojPath);

XmlNodeList projectReferenceNodes = doc.SelectNodes("//ProjectReference");

if (projectReferenceNodes != null)
{
foreach (XmlNode node in projectReferenceNodes)
{
var dependencyFilePath = node.Attributes["Include"].Value;
if (dependencyFilePath.EndsWith(".csproj"))
{
var newCsprojFilePath = Path.GetFullPath(Path.Combine(foldPath, dependencyFilePath));
new VSCSharpProjectFileInternalWatcher(newCsprojFilePath, watcherList, notifyAction);
}
}
}
}
catch (Exception e)
{
Console.WriteLine("错误: " + e.Message);
}
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The VSCSharpProjectFileInternalWatcher class is well-implemented, setting up watchers for project dependencies effectively. Enhance error handling by providing more specific error messages.

- Console.WriteLine("错误: " + e.Message);
+ Console.WriteLine($"错误: {e.GetType().Name} - {e.Message}");
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
internal class VSCSharpProjectFileInternalWatcher
{
private Action? _notify;
public VSCSharpProjectFileInternalWatcher(string csprojPath, ConcurrentDictionary<string, (FileSystemWatcher csprojWatcher, FileSystemWatcher csFileWatcher)> watcherCache, Action? notifyAction = null)
{
_notify = notifyAction;
if (!watcherCache.ContainsKey(csprojPath))
{
if (File.Exists(csprojPath))
{
(FileSystemWatcher csprojWatcher, FileSystemWatcher csFileWatcher) watcher = new();
if (!watcherCache.TryAdd(csprojPath, watcher))
{
return;
}
#if DEBUG
Console.WriteLine($"部署对 {csprojPath} 的监控. ");
#endif
var folder = Path.GetDirectoryName(csprojPath);
var fileName = Path.GetFileName(csprojPath);
var binFolder = Path.Combine(folder, "bin");
var objFolder = Path.Combine(folder, "obj");
FileSystemEventHandler handler = (sender, e) => {
if (!e.FullPath.StartsWith(binFolder) && !e.FullPath.StartsWith(objFolder))
{
_notify?.Invoke();
}
};
RenamedEventHandler reNameHandler = (sender, e) => {
if (!e.FullPath.StartsWith(binFolder) && !e.FullPath.StartsWith(objFolder))
{
_notify?.Invoke();
}
};
var csfileWatcher = new FileSystemWatcher()
{
Path = folder,
Filter = "*.cs",
EnableRaisingEvents = true,
IncludeSubdirectories = true
};
csfileWatcher.Changed += handler;
csfileWatcher.Deleted += handler;
csfileWatcher.Created += handler;
csfileWatcher.Renamed += reNameHandler;
var csprojWatcher = new FileSystemWatcher()
{
Path = folder,
Filter = fileName,
EnableRaisingEvents = true,
IncludeSubdirectories = false,
};
csprojWatcher.Changed += handler;
csprojWatcher.Renamed += reNameHandler;
csprojWatcher.Deleted += (sender, e) =>
{
if (e.FullPath == csprojPath)
{
csprojWatcher.Dispose();
csfileWatcher.Dispose();
watcherCache.TryRemove(csprojPath, out var _);
}
};
watcherCache[csprojPath] = (csprojWatcher, csfileWatcher);
MonitorDependencyProject(folder, csprojPath, watcherCache, notifyAction);
}
else
{
throw new Exception($"未找到 {csprojPath} 文件");
}
}
}
private void MonitorDependencyProject(string foldPath, string csprojPath, ConcurrentDictionary<string, (FileSystemWatcher csprojWatcher, FileSystemWatcher csFileWatcher)> watcherList, Action? notifyAction)
{
try
{
XmlDocument doc = new();
doc.Load(csprojPath);
XmlNodeList projectReferenceNodes = doc.SelectNodes("//ProjectReference");
if (projectReferenceNodes != null)
{
foreach (XmlNode node in projectReferenceNodes)
{
var dependencyFilePath = node.Attributes["Include"].Value;
if (dependencyFilePath.EndsWith(".csproj"))
{
var newCsprojFilePath = Path.GetFullPath(Path.Combine(foldPath, dependencyFilePath));
new VSCSharpProjectFileInternalWatcher(newCsprojFilePath, watcherList, notifyAction);
}
}
}
}
catch (Exception e)
{
Console.WriteLine("错误: " + e.Message);
}
}
}
catch (Exception e)
{
Console.WriteLine($"错误: {e.GetType().Name} - {e.Message}");
}

Comment on lines +7 to +66
internal class VSCSharpProjectFileWatcher
{
private Func<Task>? _execute;
private readonly HashSet<string> _mainExecuteCache;
private long _timeStamp;
private bool _needExecuted;
private readonly ConcurrentDictionary<string, (FileSystemWatcher csprojWatcher, FileSystemWatcher csFileWatcher)> _projctWatcherCache;
public VSCSharpProjectFileWatcher(string csprojPath, Func<Task>? executeAction)
{
_execute = executeAction;
_projctWatcherCache = [];
_mainExecuteCache = [];
_mainExecuteCache.Add(VSCSharpFolder.DebugPath);
_mainExecuteCache.Add(VSCSharpFolder.ReleasePath);
_mainExecuteCache.Add(VSCSharpFolder.ExecutePath);
_ = new VSCSharpProjectFileInternalWatcher(csprojPath, _projctWatcherCache, Notify);
_projctWatcherCache[csprojPath].csFileWatcher.Dispose();
}

public void Notify()
{
#if DEBUG
Console.WriteLine($"接收重建通知!");
#endif
_timeStamp = DateTime.UtcNow.Ticks / 10000;
_needExecuted = true;
}

public void StartMonitor()
{

Task.Run(async () => {

while (true)
{
if (_needExecuted == true)
{
Clean();
#if DEBUG
Console.WriteLine($"时间戳差值: {DateTime.UtcNow.Ticks / 10000 - _timeStamp}");
#endif
if (DateTime.UtcNow.Ticks / 10000 - _timeStamp > 2000)
{
_needExecuted = false;
if (_execute!=null)
{
await _execute();
}

}
await Task.Delay(800);
}
else
{
await Task.Delay(800);
}
}
});

}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The VSCSharpProjectFileWatcher class effectively initializes file watchers and handles notifications. Consider refining the delay logic to avoid hard-coded values.

- await Task.Delay(800);
+ await Task.Delay(TimeSpan.FromMilliseconds(800));
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
internal class VSCSharpProjectFileWatcher
{
private Func<Task>? _execute;
private readonly HashSet<string> _mainExecuteCache;
private long _timeStamp;
private bool _needExecuted;
private readonly ConcurrentDictionary<string, (FileSystemWatcher csprojWatcher, FileSystemWatcher csFileWatcher)> _projctWatcherCache;
public VSCSharpProjectFileWatcher(string csprojPath, Func<Task>? executeAction)
{
_execute = executeAction;
_projctWatcherCache = [];
_mainExecuteCache = [];
_mainExecuteCache.Add(VSCSharpFolder.DebugPath);
_mainExecuteCache.Add(VSCSharpFolder.ReleasePath);
_mainExecuteCache.Add(VSCSharpFolder.ExecutePath);
_ = new VSCSharpProjectFileInternalWatcher(csprojPath, _projctWatcherCache, Notify);
_projctWatcherCache[csprojPath].csFileWatcher.Dispose();
}
public void Notify()
{
#if DEBUG
Console.WriteLine($"接收重建通知!");
#endif
_timeStamp = DateTime.UtcNow.Ticks / 10000;
_needExecuted = true;
}
public void StartMonitor()
{
Task.Run(async () => {
while (true)
{
if (_needExecuted == true)
{
Clean();
#if DEBUG
Console.WriteLine($"时间戳差值: {DateTime.UtcNow.Ticks / 10000 - _timeStamp}");
#endif
if (DateTime.UtcNow.Ticks / 10000 - _timeStamp > 2000)
{
_needExecuted = false;
if (_execute!=null)
{
await _execute();
}
}
await Task.Delay(800);
}
else
{
await Task.Delay(800);
}
}
});
}
public void StartMonitor()
{
Task.Run(async () => {
while (true)
{
if (_needExecuted == true)
{
Clean();
#if DEBUG
Console.WriteLine($"时间戳差值: {DateTime.UtcNow.Ticks / 10000 - _timeStamp}");
#endif
if (DateTime.UtcNow.Ticks / 10000 - _timeStamp > 2000)
{
_needExecuted = false;
if (_execute!=null)
{
await _execute();
}
}
await Task.Delay(TimeSpan.FromMilliseconds(800));
}
else
{
await Task.Delay(TimeSpan.FromMilliseconds(800));
}
}
});
}

@NMSAzulX NMSAzulX merged commit 7fcc137 into dev.next Jun 9, 2024
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

1 participant